package cn.tworice.monitor.service;

import cn.tworice.common.framework.mail.core.MailExecutor;
import cn.tworice.common.util.AgingMap;
import cn.tworice.ip.util.IpAddrUtils;
import cn.tworice.monitor.config.MonitorProperties;
import cn.tworice.monitor.vo.AccessInfo;
import cn.tworice.monitor.vo.RequestInfo;
import com.alibaba.fastjson.JSON;
import lombok.Getter;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.regex.Pattern;

@Service
public class RequestFlowService {

    @Getter private final ConcurrentLinkedQueue<RequestInfo> queue = new ConcurrentLinkedQueue<>();

    @Resource
    private MonitorProperties monitorProperties;

    @Resource
    private MailExecutor mailExecutor;

    /**
     * 缓存访问记录
     */
    private final LinkedList<AccessInfo> accessQueue = new LinkedList<>();

    /**
     * 记录存在sql注入的记录
     */
    @Getter
    private final Map<String, String> sqlMap = new HashMap<>();

    /**
     * 被禁的Map集合
     */
    private final AgingMap<String, String> banMap = new AgingMap<>();

    /***********************************    对外公共方法      *************************************************/
    /**
     * 判断请求频率
     * @param request 请求
     * @return 是否通过
     */
    public boolean enter(HttpServletRequest request) {
        if (!monitorProperties.getFlow() || Objects.equals(request.getMethod(), "OPTIONS")) {
            return true;
        }
        // 记录请求信息
        this.storeRequestInfo(request);

        RequestInfo requestInfo = new RequestInfo();
        requestInfo.setIpAddr(IpAddrUtils.getIpAddr(request));
        if (queue.size() > monitorProperties.getStorage()) {
            queue.poll();
        }
        requestInfo.setDate(format());
        queue.add(requestInfo);
        return record(requestInfo);
    }

    /**
     * 从 HttpServletRequest 对象中提取主要信息，并存入队列中
     *
     * @param request HttpServletRequest 对象
     */
    public void storeRequestInfo(HttpServletRequest request) {

        AccessInfo info = new AccessInfo();
        info.timestamp = System.currentTimeMillis();
        // 记录请求行信息
        info.method = request.getMethod();
        info.requestURL = request.getRequestURL().toString();
        info.queryString = request.getQueryString();
        info.protocol = request.getProtocol();

        // 客户端相关信息
        info.remoteAddr = request.getRemoteAddr();
        info.remoteHost = request.getRemoteHost();
        info.remotePort = request.getRemotePort();

        // 服务器相关信息
        info.serverName = request.getServerName();
        info.serverPort = request.getServerPort();

        // 获取部分关键请求头（可以根据需要增加更多）
        String[] headerNames = {"User-Agent", "Referer", "Content-Type", "adminId"};
        for (String header : headerNames) {
            String value = request.getHeader(header);
            if (value != null) {
                info.headers.put(header, value);
            }
        }

        // 获取所有请求参数（GET 和 POST 参数）
        info.parameters.putAll(request.getParameterMap());

        // 将信息存入队列，同时维护队列大小不超过 MAX_SIZE
        synchronized (accessQueue) {
            if (accessQueue.size() >= monitorProperties.getStorage()) {
                // 淘汰最早的记录
                accessQueue.removeFirst();
            }
            accessQueue.addLast(info);
        }
    }

    /**
     * 获取当前存储的所有请求信息
     *
     * @return 一个包含所有 AccessInfo 对象的队列副本
     */
    public LinkedList<AccessInfo> getAccessQueue() {
        synchronized (accessQueue) {
            return new LinkedList<>(accessQueue);
        }
    }

    /**
     * 检查请求参数中是否包含SQL注入
     * @param request 请求
     * @return 是否通过
     */
    public boolean sqlInject(HttpServletRequest request) {
        // 获取请求路径
        String requestPath = request.getRequestURI();

        // 检查请求路径是否包含SQL注入
        if (SQL_INJECTION_PATTERN.matcher(requestPath).find()) {
            return true;
        }

        // 获取请求参数并检查是否包含SQL注入
        for (String[] paramValues : request.getParameterMap().values()) {
            for (String paramValue : paramValues) {
                if (paramValue != null && SQL_INJECTION_PATTERN.matcher(paramValue).find()) {
                    String ipAddr = IpAddrUtils.getIpAddr(request);
                    this.sqlMap.put(ipAddr, paramValue);
                    this.banMap.put(ipAddr, null, monitorProperties.getRefuse());
                    return true;
                }
            }
        }

        // 如果没有发现SQL注入，返回false
        return false;
    }

    /***********************************    对内私有方法      *************************************************/

    /**
     * 记录请求，判断该请求是否超过限制
     * @param requestInfo 当前请求
     * @return 是否通过
     */
    private boolean record(RequestInfo requestInfo) {
        if (this.banMap.containsKey(requestInfo.getIpAddr())) {
            return false;
        }
        String s = JSON.toJSONString(this.queue);
        String[] split = s.split(requestInfo.getIpAddr());
        if (split.length > monitorProperties.getLimit()) {
            banMap.put(requestInfo.getIpAddr(), requestInfo.getDate(), monitorProperties.getRefuse());
            // 发送提醒给指定邮箱
            if (monitorProperties.getEmail() != null) {
                mailExecutor.sendMail(monitorProperties.getEmail(), "检测到异常访问", "IP地址：" + requestInfo.getIpAddr() + "，时间：" + requestInfo.getDate() + "，请求次数：" + split.length + "，已封禁" + monitorProperties.getRefuse() / 1000 + "秒");
            }
            return false;
        }
        return true;
    }

    /**
     * 格式化时间
     * @return 时间
     */
    private String format() {
        GregorianCalendar calendar = new GregorianCalendar();
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        int minute = calendar.get(Calendar.MINUTE);
        return hour + ":" + minute;
    }

    /**
     * 定义一些常见的SQL注入模式
     */
    private static final Pattern SQL_INJECTION_PATTERN = Pattern.compile(
            "(?:\\b(?:select|or|update|delete|insert|truncate|alter|drop|create|exec|execute|declare|union|merge|call)\\b|\\b(?:--|;|'|\"|\\/\\*|\\*\\/|\\\\x|--|\\/\\*|\\*\\/|\\\\x).*)",
            Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL
    );
}
